Описание проекта
Вы решили открыть небольшое кафе в Москве. Оно оригинальное — гостей должны обслуживать роботы. Проект многообещающий, но дорогой. Вместе с партнёрами вы решились обратиться к инвесторам. Их интересует текущее положение дел на рынке — сможете ли вы снискать популярность на долгое время, когда все зеваки насмотрятся на роботов-официантов?
Входные данные:
Файл /datasets/rest_data.csv с данными о заведениях общественного питания Москвы.
Цель: для инвесторов подготовить исследование рынка заведений общественного питания в Москве и дать рекомендации о виде заведения, количестве посадочных мест, а также районе расположения.
Задачи
Шаг №1. Загрузите данные и подготовьте их к анализу
Загрузите данные о заведениях общественного питания Москвы. Убедитесь, что тип данных в каждой колонке — правильный, а также отсутствуют пропущенные значения и дубликаты. При необходимости обработайте их.
Путь к файлу: /datasets/rest_data.csv.
Шаг №2. Анализ данных
Сделайте общий вывод и дайте рекомендации о виде заведения, количестве посадочных мест, а также районе расположения. Прокомментируйте возможность развития сети.
Шаг №3. Подготовка презентации
Подготовьте презентацию исследования для инвесторов.
import pandas as pd
import scipy.stats as stats
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
from plotly import graph_objects as go
import seaborn as sns
import re
import warnings
warnings.filterwarnings("ignore", 'This pattern has match groups')
warnings.filterwarnings("ignore", 'This pattern is interpreted as a regular expression, and has match groups')
Чтение данных rest_data.csv
# чтение данных
rest_data = pd.read_csv('rest_data.csv')
# информация по датафрейму
rest_data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 15366 entries, 0 to 15365 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 15366 non-null int64 1 object_name 15366 non-null object 2 chain 15366 non-null object 3 object_type 15366 non-null object 4 address 15366 non-null object 5 number 15366 non-null int64 dtypes: int64(2), object(4) memory usage: 720.4+ KB
# первые пять строк датафрейма
rest_data.head()
| id | object_name | chain | object_type | address | number | |
|---|---|---|---|---|---|---|
| 0 | 151635 | СМЕТАНА | нет | кафе | город Москва, улица Егора Абакумова, дом 9 | 48 |
| 1 | 77874 | Родник | нет | кафе | город Москва, улица Талалихина, дом 2/1, корпус 1 | 35 |
| 2 | 24309 | Кафе «Академия» | нет | кафе | город Москва, Абельмановская улица, дом 6 | 95 |
| 3 | 21894 | ПИЦЦЕТОРИЯ | да | кафе | город Москва, Абрамцевская улица, дом 1 | 40 |
| 4 | 119365 | Кафе «Вишневая метель» | нет | кафе | город Москва, Абрамцевская улица, дом 9, корпус 1 | 50 |
В столбце chain заменим да/нет на булев тип True/False
# заменим в столбце chain да/нет на булев тип true/false
rest_data['chain'] = rest_data['chain'].replace('нет', False)
rest_data['chain'] = rest_data['chain'].replace('да', True)
Проверим уникальные названия типов заведений:
# проверка уникальных типов заведений
rest_data['object_type'].unique()
array(['кафе', 'столовая', 'закусочная',
'предприятие быстрого обслуживания', 'ресторан', 'кафетерий',
'буфет', 'бар', 'магазин (отдел кулинарии)'], dtype=object)
Для удобства заменим тип заведения магазин (отдел кулинарии) на кулинария, а предприятие быстрого обслуживания на фаст-фуд.
rest_data['object_type'] = rest_data['object_type'].replace('магазин (отдел кулинарии)','кулинария')
rest_data['object_type'] = rest_data['object_type'].replace('предприятие быстрого обслуживания','фаст-фуд')
В датафрейме присутствуют неявные дубликаты названий заведений. Например кафе "Шоколадница" имеет 9 различных наименований:
rest_data.loc[(rest_data['object_name'].str.contains('Шоколадница')), 'object_name'].unique()
array(['Шоколадница', 'Шоколадница Кофе Хаус', 'Кофейня «Шоколадница»',
'Кафе «Шоколадница»', 'КАФЕ «Шоколадница»', 'кафе «Шоколадница»',
'Кофейня Шоколадница', 'Шоколадница Экспресс',
'Шоколадница Кофемания'], dtype=object)
Для начала приведем все названия заведений к нижнему регистру:
# приведем строки к нижнему регистру
rest_data['object_name'] = rest_data['object_name'].str.lower()
Некоторые строки содержат названия двух объектов. Исследуем такие записи и по возможности откорректиреум их:
# некоторые строки содержат названия двух объектов. очистим такие данные:
# fridays kfc
# 15233 211435 fridays kfc True предприятие быстрого обслуживания город Москва, проспект Мира, дом 211, корпус 2 50
# по адресу находится оба заведения. Но т.к. тип заведения "предприятие быстрого питания", то уберем из строки "fridays"
rest_data['object_name'] = rest_data['object_name'].replace('fridays kfc','kfc')
# сабвей сушивок
# 2671 124655 сабвей сушивок True кафе город Москва, Ботаническая улица, дом 29, корпус 1 24
# по адресу находится оба заведения. Но т.к. тип заведения "кафе" и сушивок позиционируют себя как магазин,
# то уберем из строки "сушивок"
rest_data['object_name'] = rest_data['object_name'].replace('сабвей сушивок','сабвей')
# кафе «сабвей» старбакс
# 5742 59091 кафе «сабвей» старбакс True предприятие быстрого обслуживания город Москва, Таганская улица, дом 2 10
# 8022 96154 кафе «сабвей» старбакс True предприятие быстрого обслуживания город Москва, улица Золоторожский Вал, дом 42 25
# по указанным адресам находятся заведения сабвей
rest_data['object_name'] = rest_data['object_name'].replace('кафе «сабвей» старбакс','сабвей')
# якитория и ян примус
# 2328 24716 якитория и ян примус True ресторан город Москва, Спартаковская улица, дом 25/28, строение 1 282
# по указаному адресу находится якитория, ян приус находится в соседнем здании
rest_data['object_name'] = rest_data['object_name'].replace('якитория и ян примус','якитория')
# советские времена чебуречная ссср
# 13460 29742 советские времена чебуречная ссср True закусочная город Москва, улица Покровка, дом 50/2, строение 2 35
# по данному адресу находится заведение советские времена
rest_data['object_name'] = rest_data['object_name'].replace('советские времена чебуречная ссср','советские времена')
# шоколадница кофе хаус
# 273 25141 шоколадница кофе хаус True кафе город Москва, Тверская улица, дом 17 60
# по данному адресу находится шоколадница
rest_data['object_name'] = rest_data['object_name'].replace('шоколадница кофе хаус','шоколадница')
# вlack burger чайхона №1
# 8450 74880 вlack burger чайхона №1 True ресторан город Москва, Садовая-Самотёчная улица, дом 20, строение 1 40
# по данному адресу до 2015 года была чайхона, затем там открылся burger heroes. Оставим в названии чайхону, т.к. никаких black
# burger по этому адресу нет
rest_data['object_name'] = rest_data['object_name'].replace('вlack burger чайхона №1','чайхона №1')
# павлин мавлин чайхона №1
# павлин мавлин - чайхана, а не чайхона №1
rest_data['object_name'] = rest_data['object_name'].replace('павлин мавлин чайхона №1','павлин мавлин')
# кафе пекарня хачапури
# 970 124103 кафе пекарня хачапури True кафе город Москва, Пятницкая улица, дом 6/1, строение 1 75
# по данному адресу находится хачапурия
rest_data['object_name'] = rest_data['object_name'].replace('кафе пекарня хачапури','хачапурия')
# хачапури, одесса-мама
# 9281 166809 хачапури, одесса-мама True ресторан город Москва, улица Шаболовка, дом 14, строение 2 130
# оставим сетевое заведение хачапури
rest_data['object_name'] = rest_data['object_name'].replace('хачапури, одесса-мама','хачапури')
# нияма. пицца пи
# пицца пи является частью сети нияма
rest_data['object_name'] = rest_data['object_name'].replace('нияма. пицца пи','нияма')
# граци рагацци зю кафе
# 4767 120924 граци рагацци зю кафе True ресторан город Москва, улица Новый Арбат, дом 17 36
# оставим сетевое заведение зю кафе
rest_data['object_name'] = rest_data['object_name'].replace('граци рагацци зю кафе','зю кафе')
# шикари иль патио
# 7646 149858 шикари иль патио True бар город Москва, Большая Тульская улица, дом 11 11
# по данному адресу находятся оба заведения. однако в базе шикари больше не встречается. Мы работаем с сетью,
# поэтому оставим только иль патио
rest_data['object_name'] = rest_data['object_name'].replace('шикари иль патио','иль патио')
# метро к&к - не является заведением общепита и встречается в базе один раз. удалим строку как ошибку.
rest_data = rest_data.loc[rest_data['object_name'] != 'метро к&к'].reset_index(drop=True)
# суши тун, хруст pizza, кофе тун
# 11994 187626 суши тун, хруст pizza, кофе тун True кафе город Москва, Большая Тульская улица, дом 13 20
# суши тун - кофе тун - это одна сеть. назовем ее суши тун
rest_data['object_name'] = rest_data['object_name'].replace('суши тун, хруст pizza, кофе тун','суши тун')
# шоколадница кофемания
# 8085 25599 шоколадница кофемания True кафе город Москва, улица Арбат, дом 1 25
# по данному адресу находится шоколадница
rest_data['object_name'] = rest_data['object_name'].replace('шоколадница кофемания','шоколадница')
# кофейня кофемания, пиццерия бармалини
# 8175 24108 кофейня кофемания, пиццерия бармалини True кафе город Москва, Садовническая улица, дом 82, строение 2 90
# по данному адресу находятся оба заведения. Но бармалини не является сетевым заведением, поэтому оставим только кофеманию
rest_data['object_name'] = rest_data['object_name'].replace('кофейня кофемания, пиццерия бармалини','кофемания')
# пицца паоло и бенто wok
# 15182 205739 пицца паоло и бенто wok True кафе город Москва, город Зеленоград, Панфиловский проспект, дом 6А 30
# по данному адресу находятся оба заведения. Оставим в качестве кафе пицца паоло
rest_data['object_name'] = rest_data['object_name'].replace('пицца паоло и бенто wok','пицца паоло')
# баскин роббинс & стардогс
# 12036 174346 закусочная «баскин роббинс & стардогс» True закусочная город Москва, Бесединское шоссе, дом 15 2
# по данному адресу находятся оба заведения. В качестве закусочной оставим стардогс
rest_data['object_name'] = rest_data['object_name'].replace('баскин роббинс & стардогс','стардогs')
Теперь найдем записи, в которых заведения указаны в кавычках. Извлечем названия из кавычек, а остальную информацию удалим из названия.
# извлечем названия в кавычках и удалим остальную информацию из строки
for i in rest_data['object_name'].index:
line = re.findall('«[A-zА-я0-9 -]+»', rest_data.loc[i, 'object_name'])
if len(line) != 0:
rest_data.loc[i, 'object_name'] = line[0][1:-1]
Приведем разные названия заведений одной сети к единому названию. Чтобы избежать случайного "схлопывания" названий заведений из разных сетей, например шоколадница может объединится с кафе шоколад, исключим слова шоколад, хинкальная, сити, кофе, хачапури, гурман и кафе при азс.
for line in (rest_data[rest_data['chain'] == True]['object_name'].unique()):
if (line != 'шоколад') \
& (line != 'хинкальная') \
& (line != 'сити') \
& (line != 'кофе') \
& (line != 'хачапури') \
& (line != 'гурман') \
& (line != 'кафе при азс'):
rest_data.loc[(rest_data['object_name'].str.contains(line)) & (rest_data['chain']==True), 'object_name'] = line
В заключение, объединим названия заведений с латинскими и русскими символами. А также исправим названия заведений, в которых есть ошибки в окончаниях или содержат пробелы и дефисы.
# переименуем дубликаты названий латинские-русские нанзвания
rest_data['object_name'] = rest_data['object_name'].replace('суши вок','суши wok')
rest_data['object_name'] = rest_data['object_name'].replace('кафе шоколад','шоколад')
rest_data['object_name'] = rest_data['object_name'].replace('сабвей','subway')
rest_data['object_name'] = rest_data['object_name'].replace(['братья караваевы', 'братья караваевых'],'кулинарная лавка братьев караваевых')
rest_data['object_name'] = rest_data['object_name'].replace('иль-патио','иль патио')
rest_data['object_name'] = rest_data['object_name'].replace('krispy creme','krispy krem')
rest_data['object_name'] = rest_data['object_name'].replace('старбакс','starbucks')
rest_data['object_name'] = rest_data['object_name'].replace('кофетун','кофе тун')
rest_data['object_name'] = rest_data['object_name'].replace('maki maki','маки маки')
rest_data['object_name'] = rest_data['object_name'].replace('dunkin donuts','данкин донатс')
rest_data['object_name'] = rest_data['object_name'].replace('павлин-мавлин','павлин мавлин')
В датафрейме у заведений одной сети могут быть разные типы, например "Макдональдс":
# проблема в том, что одно и то же сетевое заведение имеет разный тип
rest_data.query('object_name=="макдоналдс"').object_type.value_counts()
фаст-фуд 117 ресторан 31 кафе 25 Name: object_type, dtype: int64
Приведем типы заведений так, чтобы у заведений одной сети был одинаковый тип. Найдем самый популярный тип заведения в сети и применим его ко всем заведениям одной сети.
# функция находит самый популярный тип сетевого заведения и применяет этот тип ко всей сети
for line in (rest_data[rest_data['chain'] == True]['object_name'].unique()):
rest_data.loc[rest_data['object_name']== line, 'object_type'] = \
rest_data.query('object_name==@line').object_type.value_counts().index[0]
# выведем случайные сетевые заведения и проверим, что у них один тип
rest_data[rest_data['chain']==True][['object_name', 'object_type', 'chain']] \
.pivot_table(index=['object_name', 'object_type']).sample(n=10)
| chain | ||
|---|---|---|
| object_name | object_type | |
| лукойл | кафе | 1.0 |
| роснефть | фаст-фуд | 1.0 |
| кофе-бин | кафе | 1.0 |
| пиццетория | кафе | 1.0 |
| burger club | кафе | 1.0 |
| изба | фаст-фуд | 1.0 |
| bubbleology | кафе | 1.0 |
| grand cru | кафе | 1.0 |
| luciano | кафе | 1.0 |
| шоколадница | кафе | 1.0 |
# Выведем уникальные названия сетевых заведений
rest_data[rest_data['chain']==True]['object_name'].unique()
array(['пиццетория', 'брусника', 'алло пицца', 'суши wok', 'тануки',
"домино'с пицца", 'готика', 'му-му', 'хлеб насущный', 'tajj mahal',
'данкин донатс', 'вареничная №1', 'шоколадница', 'теремок',
'хинкальная', 'шантимель', 'хинкальная city', 'кружка',
'примавера', 'виктория', 'академия', 'чебуречная ссср',
'макдоналдс', 'grand cru', 'чайхона №1', 'панчо пицца', 'kfc',
'subway', 'якитория', 'советские времена', 'андерсон', 'суши сет',
'шоколад', 'тирольские пироги', 'гамбринус', 'пицца фабрика',
'сити пицца', 'кофе хаус', 'кулинарная лавка братьев караваевых',
'прайм', 'пицца экспресс', 'николай', 'магнолия', 'кофе с собой',
'джаганнат', 'волконский', 'moskalyan', 'гино-но-таки',
'тратория semplice', 'ньокки', 'хижина', 'додо пицца',
'крошка картошка', 'бургер кинг', 'папа джонс', 'две палочки',
'джон джоли', 'ваби-саби', 'кофемания', 'простые вещи',
'павлин мавлин', 'тапчан', 'штолле', 'бабай клаб', 'кактус',
'темпл бар', 'хлеб&co', 'кофе-бин', 'французская выпечка',
'планета суши', 'де марко', 'илья муромец', 'тарас бульба',
'гудман', 'иль патио', 'мюнгер', 'ботик петра', 'чин чин',
'правdа кофе', 'сити', 'krispy krem', 'азбука вкуса', 'пивко',
'брудер', 'мимино', 'кофепорт', 'цинандали хинкальная',
'иль форно', 'travelers coffe', 'рецептор', 'сушишоп', 'кофе-хаус',
'баскин роббинс', 'барашка', 'пицца хат', 'мята', 'costa coffee',
'ёрш', 'стардогs', 'upside down', 'хачапурия',
'домашнее кафе сеть городских кафе', 'добрынинский и партнёры',
'менза', 'колбасофф', 'крепери де пари', 'коста кофе', 'starbucks',
'венеция', 'жан жак', 'гурмания', 'bierloga', 'штирбирлиц',
'маки-маки', 'хачапури', 'в&в бургер', 'jeffreys coffee',
'дабл би', 'золотая вобла', 'пилзнер', 'перекресток', 'руккола',
'милано пицца', 'зодиак', 'торро гриль', 'world class', 'роллофф',
'кулинарное бюро', 'билла', 'урюк', 'tokyo bay', 'пиппони',
'пицца пипони', 'сытая утка', 'икура паб', 'сварня',
'пиццерия пиу дель чибо', 'ичибан боши', 'макс бреннер',
'бутчер бизон', 'магбургер', 'prime', 'то да сё', 'пицца pomodoro',
'козловица', 'грабли', 'вьеткафе', 'оникс', 'да пино',
'старина миллер', 'хинкальная №1', 'пронто', 'паоло', 'лепешка',
'гурман', 'территория', 'городские автокофейни', 'порто мальтезе',
'квартира 44', 'хинкальная кинто', 'суп кафе', 'florentini',
'поль бейкери', 'glowsubs sandwiches', 'ми пьяче',
'пиццерия донателло', 'нияма', 'марукамэ', 'бир хаус', 'спб',
'кебаб хаус', 'paul поль', 'шашлык-машлык', 'генацвали', 'зю кафе',
'виват-пицца', 'starlite diner', 'кофе тун', 'ташир пицца',
'ресторан хинкальная', 'елки-палки', 'тамаси суши', 'burger club',
'фантоцци рус', 'изба', 'воккер', 'ганс и марта', 'кофе',
'ливан-хаус', 'лето', 'ямми микс', 'баракат', 'кафе при азс',
'космик', 'сушиман', 'япоша', 'барбарис', 'yogurt frenzy',
'bocconcino', 'помидор', 'дюшес', 'урожай', 'ян примус',
'шварцвальд', 'tutti frutti', 'меленка', 'сбарро',
'восточный базар', 'каро', 'white rabbite (белый кролик)',
'бакинский бульвар', 'healthy food', 'sushilka', 'coffeeshop',
'маки маки', 'барбекю', 'обжорный ряд', 'тайм авеню', 'wok & box',
'кафе при азс газпромнефть', 'торнадо', 'бенто wok', 'бургер клаб',
'bp', 'wokker', 'ариана', 'ванвок', 'il patio', 'пражечка',
'блинная', 'correas', 'мистер картошка', 'сим-сим', 'югос',
'марчеллис', 'кафе песто и митлес', 'молли гвинз',
'дорогая я перезвоню', 'm cafe хинкальная', 'marrakesh хинкальная',
'the terrace', 'пончиковое кафе икеа ikea', 'ресторан икеа ikea',
'black & white', 'роснефть', 'cookhouse',
'мск московская сеть кальянных', 'cofix', 'пикколо', 'милти',
'васаби', 'грузинская кухня эzо хинкальная',
'хачапури, одесса -мама', 'fridays', 'panda express',
'ирландский паб', 'сказка', 'вкусняшка', 'суши тун', 'кофейня',
'лукойл', 'мираторг', 'bubbleology', 'boobo', 'luciano',
'кальянная f-lounge', 'beverly hills diner', 'lavkalavka',
'хлебница пекарня', 'osteria mario', 'мясоroob',
'газпромнефтьцентр', 'my box', 'сувлаки', 'movenpick', 'marmalato'],
dtype=object)
# проверка явных дубликатов строк
print(
'Количество явных дубликатов равно:',
rest_data.duplicated().sum()
)
Количество явных дубликатов равно: 0
# проверка неявных дубликатов
print(
'Количество неявных дубликатов равно:',
rest_data[['object_name', 'chain', 'object_type', 'address', 'number']].duplicated().sum()
)
rest_data = rest_data[~(rest_data[['object_name', 'chain', 'object_type', 'address', 'number']].duplicated())] \
.reset_index(drop=True)
Количество неявных дубликатов равно: 88
rest_data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 15277 entries, 0 to 15276 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 15277 non-null int64 1 object_name 15277 non-null object 2 chain 15277 non-null bool 3 object_type 15277 non-null object 4 address 15277 non-null object 5 number 15277 non-null int64 dtypes: bool(1), int64(2), object(3) memory usage: 611.8+ KB
Описание данных
Таблица rest_data:
id — идентификатор объекта;object_name — название объекта общественного питания;chain — сетевой ресторан;object_type — тип объекта общественного питания;address — адрес;number — количество посадочных мест.В таблице rest_data 15277 записей.
Пропусков и явных дубликатов не обнаружено. Без учета ID было обнаружено и удалено 88 дубликатов.
Тип данных в столбце chain был заменен на bool. Тип данных в остальных столбцах оставлен как есть.
Названия столбцов соответствуют стилистическим нормам и оставлены как есть.
Названия сетевых заведений были обработаны, приведены к единому названию: убраны кавычки, названия из латинских и русских символов приведены к единому формату, тип заведений установлен одинаковым для всей сети.
Чтение данных из внешнего источника
# считываем данные с информацией по районам из файла mosgaz-streets.csv
streets = pd.read_csv('https://hubofdata.ru/dataset/4fee7193-2ead-4a49-ac2d-63928ba7a0f9/resource/9044e34d-2904-48e4-841a-a97b41a9f200/download/mosgaz-streets.csv')
# информация по датафрейму
streets.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 4398 entries, 0 to 4397 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 streetname 4398 non-null object 1 areaid 4398 non-null int64 2 okrug 4398 non-null object 3 area 4398 non-null object dtypes: int64(1), object(3) memory usage: 137.6+ KB
# переименуем название столбцов для удобства объединения по столбцу street
streets.columns = ['street', 'areaid', 'okrug', 'area']
# проверка дубликатов строк
streets.duplicated().sum()
8
# удаление дубликатов
streets = streets.drop_duplicates().reset_index(drop=True)
streets.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 4390 entries, 0 to 4389 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 street 4390 non-null object 1 areaid 4390 non-null int64 2 okrug 4390 non-null object 3 area 4390 non-null object dtypes: int64(1), object(3) memory usage: 137.3+ KB
В таблице streets 4390 записей.
Пропусков не обнаружено. Дубликаты удалены.
Типы данных в столбцах оставлены как есть.
Названия столбцов соответствуют стилистическим нормам.
# построим столбчатые диаграммы по видам заведений
fig = px.bar(
rest_data.groupby('object_type', as_index=False).agg({'id':'count'})[['object_type', 'id']].sort_values(by='id', ascending=False),
x='object_type',
y='id',
color='object_type',
labels=dict(object_type='вид объекта', id='количество'),
text = 'id'
)
fig.update_layout(
title={'text':'Количество объектов общественного питания по их видам', 'x':0.5}
)
for trace, percent in zip(fig.data, (rest_data['object_type'].value_counts()/len(rest_data)*100).round(1).astype('str') + '%' ) :
trace.name = trace.name + ' (' + percent+ ')'
fig.show()
# построим круговую диаграмму по видам заведений
fig = go.Figure(
data=go.Pie(
labels=rest_data['object_type'].value_counts().reset_index()['index'],
values=rest_data['object_type'].value_counts()
)
)
fig.update_layout(
title={'text':'Соотношение объектов общественного питания по их видам', 'x':0.5}
)
fig.show()
Из графиков следует, что больше всего заведений общественного питания вида кафе(40.5%), столовые(16.9%) и рестораны(14.3%). Кулинарии представлены в наименьшем количестве.
# столбчатая диаграмма сетевых / несетевых заведений
fig = px.bar(
rest_data.groupby('chain', as_index=False).agg({'id':'count'})[['chain', 'id']].sort_values(by='id', ascending=True),
x=['сетевое', 'несетевое'],
y='id',
color=['сетевое', 'несетевое'],
labels=dict(x='вид заведения', id='количество', color='вид заведения'),
text = 'id'
)
fig.update_layout(
title={'text':'Соотношение сетевых и несетевых заведений', 'x':0.5}
)
for trace, percent in zip(fig.data, (rest_data['chain'].value_counts()/len(rest_data)*100).round(1).astype('str') + '%' ) :
trace.name = trace.name + ' (' + percent+ ')'
fig.show()
# столбчатая диаграмма сетевых/несетевых заведений по категориям
fig = go.Figure()
fig.add_trace(go.Bar(
x = rest_data[rest_data['chain']==True].groupby('object_type').agg('count')['id'].index,
y = rest_data[rest_data['chain']==True].groupby('object_type').agg('count')['id'],
text = rest_data[rest_data['chain']==True].groupby('object_type').agg('count')['id'],
name='сетевое'
))
fig.add_trace(go.Bar(
x = rest_data[rest_data['chain']==False].groupby('object_type').agg('count')['id'].index,
y = rest_data[rest_data['chain']==False].groupby('object_type').agg('count')['id'],
text = rest_data[rest_data['chain']==False].groupby('object_type').agg('count')['id'],
name='несетевое'
))
fig.update_layout(
title={'text':'Количество сетевых и несетевых заведений по количеству и виду', 'x':0.5},
xaxis_title="вид заведения",
yaxis_title="количество"
)
fig.show()
Несетевых заведений почти в четыре раза больше чем сетевых. Всего несетевых 12316 заведений, сетевых - 2961.
По категориям несетевых заведений также больше.
При этом сетевые заведения в основном представлены в виде кафе, ресторанов, фаст-фудов.
Несетевых кафе больше сетевых в 3,2 раза.
Несетевых ресторанов больше сетевых в 4,0 раза.
Несетевых фаст-фудов больше сетевых в 1,1 раза.
# соберем датафрейм с % сетевых заведений по их типам
chain_rate = pd.DataFrame(rest_data[rest_data['chain']==True]['object_type'].value_counts()/rest_data['object_type']. \
value_counts()*100).round(1).sort_values(by='object_type', ascending=False).reset_index()
chain_rate.columns = ['object_type', 'rate']
chain_rate['rate'] = chain_rate['rate'].fillna(0)
# построим столбчатые диаграммы по % заведений
fig = px.bar(
chain_rate,
x='object_type',
y='rate',
color='object_type',
labels=dict(object_type='вид объекта', rate='% заведений'),
text = chain_rate['rate'].astype('str')+'%'
)
fig.update_layout(
title={'text':'Количество объектов общественного питания по их видам', 'x':0.5}
)
fig.show()
Сетевая принадлежность наиболее характерна для:
Столовые и буфеты обычно не представлены в виде сетевых заведений. Это связано с тем, что данные типы заведений являются объектами обслуживающих производств и хозяйств и находятся на территории предприятий, бизнес-центров, в учреждениях культуры и досуга и т.д.
# построим диаграмму размаха посадочных мест
fig = go.Figure(go.Box(x=rest_data[rest_data['chain']==True]['number']))
fig.update_layout(title={'text':'Диаграмма размаха посадочных мест сетевых заведений', 'x':0.5},
xaxis_title="Количество посадочных мест")
fig.show()
# построим гистограмму посадочных мест сетвых заведений
fig = go.Figure(go.Histogram(x=rest_data[rest_data['chain']==True]['number']))
fig.update_layout(title={'text':'Гистограмма посадочных мест сетевых заведений', 'x':0.5},
xaxis_title="Количество посадочных мест")
fig.show()
# определим категории кол-ва заведений и кол-ва посадочных мест
chain_type = rest_data[rest_data['chain']==True].groupby('object_name', as_index=False). \
agg({'id':'count', 'number':'median'})
chain_type.columns = ['object_name', 'count', 'number']
# примем границу разделения много-мало 50% квантиль:
q_count = np.percentile(chain_type['count'], 50)
q_number = np.percentile(rest_data[rest_data['chain']==True]['number'], 50)
def chain_sort(row):
global q_count
global q_number
if (row['count'] > q_count) & (row['number'] > q_number):
return 'Много заведений и много посадочных мест'
if (row['count'] > q_count) & (row['number'] <= q_number):
return 'Много заведений и мало посадочных мест'
if (row['count'] <= q_count) & (row['number'] > q_number):
return 'Мало заведений и много посадочных мест'
if (row['count'] <= q_count) &( row['number'] <= q_number):
return 'Мало заведений и мало посадочных мест'
chain_type['category'] = chain_type.apply(chain_sort, axis=1)
print('Медианное количество заведений:', q_count)
print('Медианное количество посадочных мест:', q_number)
chain_type['category'].value_counts()
Медианное количество заведений: 3.0 Медианное количество посадочных мест: 40.0
Мало заведений и много посадочных мест 91 Мало заведений и мало посадочных мест 76 Много заведений и много посадочных мест 58 Много заведений и мало посадочных мест 51 Name: category, dtype: int64
# Построим совместную диаграмму зависимости числа мест от количества заведений в сети
plt.style.use('ggplot')
p = sns.jointplot(x='number', y='object_name', data=rest_data[rest_data['chain']==True].groupby('object_name'). \
agg({'object_name':'count', 'number':'median'}))
p.ax_joint.axhline(np.percentile(chain_type['count'], 50), linestyle='--', color='black')
p.ax_joint.axvline(np.percentile(rest_data[rest_data['chain']==True]['number'], 50), linestyle='--', color='black')
p.ax_joint.text(220,7, 'среднее кол-во заведений в сети')
p.ax_joint.text(35,80, 'среднее кол-во мест', rotation=90)
p.set_axis_labels('Среднее кол-во посадочных мест', 'Кол-во заведений в сети')
p.fig.suptitle('Зависимость кол-ва посадочных мест от числа заведений в сети')
p.fig.subplots_adjust(top=0.95)
p.fig.set_figwidth(12)
p.fig.set_figheight(4)
Из анализа следует, что для сетевых заведений характерно мало заведений с большим числом посадочных мест в каждом. Медианное количество мест в заведении равно 40. Медианное количество заведений в сети рано трем.
# построим столбчатые диаграммы по видам заведений
fig = px.bar(
rest_data.groupby('object_type', as_index=False).agg({'number':'mean'})[['object_type', 'number']].sort_values(by='number', ascending=False),
x='object_type',
y='number',
color='object_type',
labels=dict(object_type='тип заведения', number='среднее количество мест'),
text = rest_data.groupby('object_type', as_index=False).agg({'number':'mean'})['number'].round(1) \
.sort_values(ascending=False).astype('str')
)
fig.update_layout(
title={'text':'Cреднее количество посадочных мест в заведении', 'x':0.5}
)
fig.show()
# построим диаграммы разброса посадочных мест по типам заведений
fig = go.Figure()
for i in list(rest_data['object_type'].unique()):
fig.add_trace(go.Box(x=rest_data.query('object_type==@i')['number'], name=i))
fig.update_layout(title={'text':'Cреднее количество посадочных мест в заведении', 'x':0.5},
yaxis_title="Тип заведения",
xaxis_title="Количество посадочных мест")
fig.show()
# описание количества мест по типам заведений
rest_data.groupby('object_type')['number'].describe().sort_values(by='mean')
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| object_type | ||||||||
| кулинария | 198.0 | 4.323232 | 8.105683 | 0.0 | 0.0 | 0.0 | 4.0 | 50.0 |
| закусочная | 293.0 | 6.675768 | 16.325897 | 0.0 | 0.0 | 0.0 | 8.0 | 216.0 |
| кафетерий | 348.0 | 9.146552 | 15.062170 | 0.0 | 0.0 | 6.0 | 12.0 | 200.0 |
| фаст-фуд | 2136.0 | 22.804307 | 39.403825 | 0.0 | 0.0 | 6.0 | 30.0 | 580.0 |
| кафе | 6165.0 | 38.970479 | 37.058004 | 0.0 | 15.0 | 30.0 | 50.0 | 533.0 |
| бар | 819.0 | 43.158730 | 68.000965 | 0.0 | 20.0 | 34.0 | 50.0 | 1700.0 |
| буфет | 564.0 | 52.271277 | 56.713413 | 0.0 | 15.0 | 32.0 | 80.0 | 320.0 |
| ресторан | 2176.0 | 100.165901 | 96.587944 | 0.0 | 48.0 | 80.0 | 120.0 | 1500.0 |
| столовая | 2578.0 | 130.206749 | 94.964693 | 0.0 | 50.5 | 103.0 | 200.0 | 1400.0 |
В среднем наибольшее количество посадочных мест имеют столовые - 130 мест и рестораны - 100 мест. Наименьшее количество мест имеют кулинарии и закусочные.
# выделим названия улиц в отдельный столбец
rest_data['street'] = rest_data['address'].apply(lambda x: x.split(',')[1])
rest_data['street'] = rest_data['street'].map(str.strip)
rest_data.head()
| id | object_name | chain | object_type | address | number | street | |
|---|---|---|---|---|---|---|---|
| 0 | 151635 | сметана | False | кафе | город Москва, улица Егора Абакумова, дом 9 | 48 | улица Егора Абакумова |
| 1 | 77874 | родник | False | кафе | город Москва, улица Талалихина, дом 2/1, корпус 1 | 35 | улица Талалихина |
| 2 | 24309 | академия | False | кафе | город Москва, Абельмановская улица, дом 6 | 95 | Абельмановская улица |
| 3 | 21894 | пиццетория | True | кафе | город Москва, Абрамцевская улица, дом 1 | 40 | Абрамцевская улица |
| 4 | 119365 | вишневая метель | False | кафе | город Москва, Абрамцевская улица, дом 9, корпус 1 | 50 | Абрамцевская улица |
# выберем топ-10 улиц по количеству заведений
top10_streets = rest_data.groupby('street').agg({'id': 'count'}).sort_values(by = 'id', ascending = False).reset_index()
top10_streets = top10_streets[top10_streets['street'] != 'город Зеленоград']
top10_streets = top10_streets[top10_streets['street'] != 'поселение Сосенское']
top10_streets = top10_streets.head(10)
top10_streets.columns = ['street', 'number']
# выведем график топ-10 улиц
fig = px.bar(
top10_streets,
x='street',
y='number',
color='street',
labels=dict(street='улица', number='количество'),
title='График топ-10 улиц по количеству объектов общественного питания',
text = top10_streets['number']
)
fig.show()
# выделим номера зданий для определения их положения на улице
building = rest_data['address'].apply(lambda x: re.findall('\d+', x.split('дом')[-1]))
for i in building.index:
if len(building[i]) == 0:
building[i] = np.nan
else:
building[i] = building[i][0]
rest_data['building'] = building.astype(int)
# получим датасет с топ-10 улиц и их адресами
rest_sort = rest_data[(rest_data['street']=='проспект Мира') |\
(rest_data['street']=='Профсоюзная улица') |\
(rest_data['street']=='Ленинградский проспект') |\
(rest_data['street']=='Пресненская набережная') |\
(rest_data['street']=='Варшавское шоссе') |\
(rest_data['street']=='Ленинский проспект') |\
(rest_data['street']=='проспект Вернадского') |\
(rest_data['street']=='Кутузовский проспект') |\
(rest_data['street']=='Каширское шоссе') |\
(rest_data['street']=='Кировоградская улица')]
# выведем плотность распределения задний вдоль улицы
fig = px.strip(rest_sort, x='building', y='street', color='street')
fig.update_layout(title={'text':'Плотность распределения заведений вдоль улицы', 'x':0.5},
yaxis_title="улица",
xaxis_title="номер здания",
showlegend=False)
# объединим топ-10 улиц с датафреймом мосгаза для получения районов
streets_okrug = top10_streets.merge(streets, on='street', how='left')
# список районов, в которых расположены топ 10 улиц по заведениям
streets_okrug['area'].unique()
array(['Алексеевский район', 'Ярославский Район', 'Район Марьина роща',
'Останкинский район', 'Район Ростокино', 'Район Свиблово',
'Мещанский район', 'Академический район', 'Район Черемушки',
'Район Ясенево', 'Район Коньково', 'Обручевский район',
'Район Теплый Стан', 'Район Аэропорт', 'Район Беговой',
'Хорошевский район', 'Район Сокол', 'Пресненский район',
'Район Чертаново Центральное', 'Район Чертаново Северное',
'Район Чертаново Южное', 'Донской район',
'Район Нагатино-Садовники', 'Нагорный район',
'Район Северное Бутово', 'Район Южное Бутово',
'Район Проспект Вернадского', 'Район Тропарево-Никулино',
'Район Гагаринский', 'Ломоносовский район', 'Район Якиманка',
'Район Раменки', 'Район Дорогомилово', 'Район Фили-Давыдково',
'Район Москворечье-Сабурово', 'Район Орехово-Борисово Южное',
'Район Орехово-Борисово Северное'], dtype=object)
Из графиков следует, что наибольшее количество объектов располагается на улицах:
При определении положения объекта на улице следует учитывать плотность заведений вдоль улицы. Наиболее часто расположены заведения:
# получим список улиц с одним заведением и объединим с датафреймом мосгаза для получения районов
one_rest_street = rest_data.groupby('street').agg({'id': 'count'}).sort_values(by = 'id', ascending = False).reset_index()
one_rest_street.columns = ['street', 'number']
one_rest_okrug = one_rest_street.merge(streets, on='street', how='left')
one_rest_okrug = one_rest_okrug.loc[one_rest_okrug['number']==1]
one_rest_okrug = one_rest_okrug.dropna().reset_index(drop=True)
print('Число улиц с одним объектом общественного питания равно:', len(one_rest_okrug))
Число улиц с одним объектом общественного питания равно: 560
# список районов, в которых расположены топ 10 улиц по заведениям
one_rest_okrug['area'].unique()
array(['Район Кунцево', 'Район Аэропорт', 'Мещанский район',
'Район Кузьминки', 'Район Измайлово', 'Район Соколиная Гора',
'Район Сокольники', 'Район Южное Бутово', 'Район Марьина роща',
'Нижегородский район', 'Район Якиманка', 'Район Солнцево',
'Район Свиблово', 'Район Митино', 'Район Хорошево-Мневники',
'Район Люблино', 'Район Выхино-Жулебино', 'Басманный район',
'Район Покровское-Стрешнево', 'Район Северное Бутово',
'Рязанский район', 'Красносельский район', 'Тверской район',
'Район Сокол', 'Бутырский район', 'Тимирязевский Район',
'Даниловский район', 'Хорошевский район', 'Район Замоскворечье',
'Район Печатники', 'Район Внуково', 'Район Лефортово',
'Район Дорогомилово', 'Таганский район', 'Район Перово',
'Бескудниковский Район', 'Район Щукино', 'Район Богородское',
'Район Раменки', 'Район Хамовники', 'Район Ростокино',
'Район Текстильщики', 'Алексеевский район', 'Район Арбат',
'Район Преображенское', 'Донской район', 'Останкинский район',
'Войковский Район', 'Пресненский район', 'Район Северный',
'Район Фили-Давыдково', 'Район Очаково-Матвеевское',
'Район Филевский Парк', 'Лосиноостровский район',
'Савеловский район', 'Район Чертаново Южное', 'Южнопортовый Район',
'Головинский район', 'Район Царицыно', 'Район Косино-Ухтомский',
'Район Москворечье-Сабурово', 'Район Гольяново',
'Район Ново-Переделкино', 'Район Ясенево',
'Район Северное Измайлово', 'Район Восточное Измайлово',
'Можайский Район', 'Район Котловка', 'Район Беговой',
'Ярославский Район', 'Бабушкинский район',
'Район Чертаново Северное', 'Алтуфьевский район',
'Район Новогиреево', 'Дмитровский район', 'Район Коптево',
'Нагорный район', 'Район Зюзино', 'Район Куркино',
'Район Бирюлево Восточное', 'Район Западное Дегунино',
'Район Ивановское', 'Район Марьино', 'Район Левобережный',
'Молжаниновский район', 'Район Южное Тушино', 'Район Отрадное',
'Район Гагаринский', 'Район Матушкино-Савелки', 'Район Строгино',
'Район Черемушки', 'Район Северное Медведково', 'Район Восточный',
'Район Нагатино-Садовники', 'Район Метрогородок', 'Район Коньково',
'Район Марфино', 'Обручевский район', 'Район Южное Медведково'],
dtype=object)
Получен список из 560 улиц, на которых находится по одному заведению общественного питания. При определении положения будущего заведения следует избегать выбора этих улиц, как наименее популярных среди посетителей.
Презентация: https://disk.yandex.ru/i/_JhQyognRcfzWg
С целью подготовить исследование рынка заведений общественного питания в Москве и дать рекомендации о виде заведения, количестве посадочных мест, а также районе расположения в данном анализе были проделаны следующие задачи:
По результатам анализа было определено, что больше всего заведений общественного питания вида кафе(40.3%), столовые(16.9%) и рестораны(14.4%). Кулинарии представлены в наименьшем количестве.
Несетевых заведений почти в четыре раза больше чем сетевых. Всего несетевых 12316 заведений, сетевых - 2961. По категориям несетевых заведений также больше.
При этом сетевые заведения в основном представлены в виде кафе, ресторанов, фаст-фудов.
Сетевая принадлежность наиболее характерна для:
Для сетевых заведений характерно мало заведений с большим числом посадочных мест в каждом. Медианное количество мест в заведении равно 40. Медианное количество заведений в сети равно трем.
В среднем наибольшее количество посадочных мест имеют столовые - 130 мест и рестораны - 100 мест. Наименьшее количество мест имеют кулинарии и закусочные.
Наибольшее количество объектов располагается на улицах:
При определении положения объекта на улице следует учитывать плотность заведений вдоль улицы. Наиболее плотно находятся заведения:
Рекомендации по выбору типа заведения
При определении типа заведения, в котором гостей обслуживают роботы, наиболее предпочтительным является кафе со средним количеством мест – 38.
Можно запланировать создание сети кафе до трех заведений.
Для выбора района рекомендуется рассмотреть топ-10 улиц с наибольшим количеством заведений, т.к. кафе на этих улицах пользуются спросом у населения. Наиболее популярные улицы: проспект Мира, Профсоюзная улица, Ленинградский проспект. При выборе места объекта необходимо учитывать плотность распределения заведений. Вероятно, что зоны с наибольшей плотностью заведений наиболее популярны среди посетителей.